Skip to content

fix: 长会话任务锚点重注入 + 过早 end_turn 提示,缓解任务跑偏#1309

Open
universe-hcy wants to merge 1 commit into
claude-code-best:mainfrom
universe-hcy:avoid_long_task_attention_degradation
Open

fix: 长会话任务锚点重注入 + 过早 end_turn 提示,缓解任务跑偏#1309
universe-hcy wants to merge 1 commit into
claude-code-best:mainfrom
universe-hcy:avoid_long_task_attention_degradation

Conversation

@universe-hcy

@universe-hcy universe-hcy commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

现象:
长开发任务跑到一半反复"停住"(像卡顿),由于这个任务并非我完全关心的,在每次卡住后我按顺序发了 6 条催促:

# 行号 时间(UTC) 催促内容
1 177 11:32 目前开发进度怎么样了
2 186 11:43 目前开发到哪个阶段了
3 218 11:54 继续执行后续的工作
4 253 12:02 自动执行后续的所有流程
5 264 12:09 继续完成未完成的任务
6 281 12:16 继续完成当前任务

后期模型"忘记任务要做什么"、编辑把文件写出"重复的、不可能编译的行",陷入自我核实打转。执行 /rewind 退回后恢复正常。这个现象我先前在qwen模型上遇到过几次,属于是模型注意力被稀释后导致的任务锚点漂移轨迹问题,目前看起来opus 4.8对于长任务 + 连续无锚点注入也会出现这个问题。

由于无法保障用户都知道存在模型注意力被稀释后导致的任务锚点漂移轨迹问题,同时用户并行使用agent,并不一定能够对当前任务足够重视,从而在长任务卡住时也可能出现连续输出“继续 ”的情况,期望在harness层进行一定的保障。下面我采用了token 友好的方式,对该问题进行修复

修复方案:
长会话反复无锚点催促("继续/接着做")会导致任务锚点衰减、模型过早
end_turn 打转。新增两层客户端护栏(均 token 友好):

  • 层3 触发式锚点重注入:仅当最新用户消息是短的无锚点催促且存在未完成 任务时,注入一次 ephemeral 未完成任务清单(工具循环 内不重复注入,无每轮成本)。
  • 层2 过早 end_turn 提示:turn 自然完成且仍有 in_progress 任务时,显示 0-token 本地 system 提示(不发给模型)。

Summary by CodeRabbit

  • New Features

    • Added reminders that resurface unfinished tasks when you send short “continue” or “keep going” prompts.
    • Task reminders prioritize active work, include pending items, and summarize additional tasks when the list is long.
    • Added an end-of-turn warning when active tasks remain unfinished, including task details and pending-task counts.
  • Bug Fixes

    • Reminder and warning generation now fails gracefully without interrupting normal conversations or turn completion.

长会话反复无锚点催促("继续/接着做")会导致任务锚点衰减、模型过早
end_turn 打转。新增两层客户端护栏(均 token 友好):
- 层3 触发式锚点重注入:仅当最新用户消息是短的无锚点催促且存在未完成
  任务时,注入一次 ephemeral <system-reminder> 未完成任务清单(工具循环
  内不重复注入,无每轮成本)。
- 层2 过早 end_turn 提示:turn 自然完成且仍有 in_progress 任务时,显示
  0-token 本地 system 提示(不发给模型)。

Co-Authored-By: claude-opus-4-8[1m] <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds task reminder utilities and tests, reinjects unfinished-task reminders for short continuation nudges, and appends unfinished-task warnings to the transcript after non-aborted turns.

Changes

Task reminder flow

Layer / File(s) Summary
Task reminder builders and coverage
src/services/api/taskAnchorReminder.ts, src/services/api/__tests__/taskAnchorReminder.test.ts
Adds anchorless-nudge detection, unfinished-task reminder formatting, unfinished-task notice formatting, and tests for ordering, filtering, overflow, and empty results.
API anchor reinjection
src/services/api/claude.ts
Detects short continuation nudges, retrieves unfinished tasks, and appends a reminder as an ephemeral metadata user message.
REPL completion notice
src/screens/REPL.tsx
After a non-aborted query, builds an unfinished-task notice and appends it as a local warning system message.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: claude-code-best

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant queryModel
  participant TaskUtilities
  participant ClaudeAPI
  User->>queryModel: send short continuation nudge
  queryModel->>TaskUtilities: read unfinished tasks
  queryModel->>ClaudeAPI: send nudge with task reminder
  ClaudeAPI-->>queryModel: stream query response
  queryModel-->>User: continue task processing
Loading
sequenceDiagram
  participant REPL
  participant TaskUtilities
  participant Transcript
  REPL->>TaskUtilities: read current task list after query completion
  TaskUtilities-->>REPL: unfinished tasks
  REPL->>Transcript: append warning notice
  Transcript-->>User: display unfinished-task warning
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the two main changes: task-anchor reinjection and early end_turn warnings to reduce task drift.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/screens/REPL.tsx`:
- Around line 3533-3536: Update the dynamic imports in the notice injection
within REPL to use the relative paths ../utils/tasks.js and
../services/api/taskAnchorReminder.js instead of src/ aliases, while preserving
the existing imported symbols and Promise.all flow.

In `@src/services/api/taskAnchorReminder.ts`:
- Around line 20-25: Update isAnchorlessNudge to reject trimmed text containing
an explicit task anchor before applying ANCHORLESS_NUDGE_PATTERN, while
preserving existing length and nudge-word validation. Reuse the existing
task-reference detection symbol if available, and add regression coverage
confirming inputs such as “continue Task `#2`” return false.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9af9169d-beab-4a26-86e1-3efe3884e075

📥 Commits

Reviewing files that changed from the base of the PR and between 34b3dc9 and e3b15d0.

📒 Files selected for processing (4)
  • src/screens/REPL.tsx
  • src/services/api/__tests__/taskAnchorReminder.test.ts
  • src/services/api/claude.ts
  • src/services/api/taskAnchorReminder.ts

Comment thread src/screens/REPL.tsx
Comment on lines +3533 to +3536
const [{ listTasks, getTaskListId }, { buildUnfinishedTaskNotice }] = await Promise.all([
import('src/utils/tasks.js'),
import('src/services/api/taskAnchorReminder.js'),
]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

fd -HI -t f . | rg '/(tsconfig[^/]*\.json|package\.json|bunfig\.toml)$'
rg -n -C2 '"(baseUrl|paths)"|src/' \
  --glob 'tsconfig*.json' \
  --glob 'package.json' \
  --glob 'bunfig.toml' .

Repository: claude-code-best/claude-code

Length of output: 18729


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Top-level package manifests/configs:"
fd -HI -t f '/^(package\.json|tsconfig\.json|bunfig\.toml|vite\.config\.[^/]+)$' -d 2 . | sed 's#^\./##' | sort

echo
echo "Top-level package.json scripts and imports:"
python3 - <<'PY'
import json
from pathlib import Path
p=Path('package.json')
if p.exists():
    data=json.loads(p.read_text())
    print(json.dumps({"name": data.get("name")} | {k:data.get(k) for k in ["scripts","imports","type","tsup"]}, indent=2, sort_keys=True))
else:
    print("no package.json")
PY

echo
echo "Top-level tsconfig relevant fields:"
python3 - <<'PY'
import json
from pathlib import Path
p=Path('tsconfig.json')
if p.exists():
    data=json.loads(p.read_text())
    print(json.dumps({k:data.get(k) for k in ["extends","compilerOptions","include","exclude","references"]}, indent=2, sort_keys=True))
else:
    print("no top-level tsconfig.json")
PY

echo
echo "Top-level bunfig relevant:"
if [ -f bunfig.toml ]; then cat -n bunfig.toml; else echo "no bunfig.toml"; fi

echo
echo "Other root aliases/imports:"
rg -n -C2 '"imports"|"alias"|[Aa]lias|root:\s*[^#]+|baseUrl|resolve|alias:' --glob 'package.json' --glob 'tsconfig*.json' --glob 'vite.config.*' --glob 'bunfig.toml' .

Repository: claude-code-best/claude-code

Length of output: 7679


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import json, re
p='package.json'
data=json.load(open(p))
root=re.compile(r'^\s*root\s*=?\s*["\']?([^"'\n\#]+)["\']?\s*$|^\s*root\s*=\s*["\']?([^"'\n\#]+)["\']?\s*$')
tsconfig=json.load(open('tsconfig.json'))
files=[]
for f in ['src/utils/tasks.js', 'src/services/api/taskAnchorReminder.js']:
    try:
        from pathlib import Path
        files.append((f, Path(f).exists()))
    except Exception as e:
        files.append((f, f'<error: {e}>'))
print(json.dumps({
    "root_root_alias": re.search(root, open('bunfig.toml').read()).group(1) if root.search(open('bunfig.toml').read()) else None,
    "package_root_alias": data.get("root"),
    "tsconfig_paths_src": tsconfig.get("compilerOptions", {}).get("paths", {}).get("src/*"),
    "files_exist": dict(files)
}, indent=2))
PY

Repository: claude-code-best/claude-code

Length of output: 424


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "bunfig root settings:"
python3 - <<'PY'
from pathlib import Path
p=Path('bunfig.toml')
text=p.read_text()
for line in text.splitlines():
    s=line.strip()
    if s.startswith('root'):
        print(line)
print(bool(p.exists()))
PY

echo
echo "package root settings:"
python3 - <<'PY'
import json
data=json.load(open('package.json'))
print(data.get('root'))
PY

echo
echo "tsconfig src alias:"
python3 - <<'PY'
import json
data=json.load(open('tsconfig.json'))
print(data.get('compilerOptions',{}).get('paths',{}).get('src/*'))
PY

echo
echo "target files exist:"
python3 - <<'PY'
from pathlib import Path
for f in ['src/utils/tasks.js', 'src/services/api/taskAnchorReminder.js']:
    print(f"{f}: {Path(f).exists()}")
PY

Repository: claude-code-best/claude-code

Length of output: 359


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Search exact specifier targets:"
rg -n "'src/utils/tasks\.js'|'src/services/api/taskAnchorReminder\.js'|utils/tasks|taskAnchorReminder" --glob '!node_modules' .

echo
echo "Top-level manifest root package files:"
fd -HI -t f '^root\.js$|^src$' . -d 4 | sed 's#^\./##'

echo
echo "REPL snippet context:"
sed -n '3525,3550p' src/screens/REPL.tsx 2>/dev/null || true

Repository: claude-code-best/claude-code

Length of output: 4906


Use relative dynamic imports for the notice injection.

src/* is only configured in TypeScript and Vite config; Bun package imports are not configured with a src/ alias, so these bare imports can resolve differently at native Bun runtime. Use ../utils/tasks.js and ../services/api/taskAnchorReminder.js instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/screens/REPL.tsx` around lines 3533 - 3536, Update the dynamic imports in
the notice injection within REPL to use the relative paths ../utils/tasks.js and
../services/api/taskAnchorReminder.js instead of src/ aliases, while preserving
the existing imported symbols and Promise.all flow.

Comment on lines +20 to +25
export function isAnchorlessNudge(text: string): boolean {
const trimmed = text.trim()
if (!trimmed || trimmed.length > MAX_NUDGE_CHARS) {
return false
}
return ANCHORLESS_NUDGE_PATTERN.test(trimmed)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reject nudges that include a task anchor.

Line 25 treats any short text containing a nudge word as anchorless. For example, continue Task #2`` matches and injects a reminder telling the model to continue the highest-priority task, overriding the user’s explicit task selection. Exclude explicit task references (and add regression coverage for them).

Proposed direction
+const TASK_REFERENCE_PATTERN = /(?:task|任务)\s*#?\d+\b/i
+
 export function isAnchorlessNudge(text: string): boolean {
   const trimmed = text.trim()
   if (!trimmed || trimmed.length > MAX_NUDGE_CHARS) {
     return false
   }
-  return ANCHORLESS_NUDGE_PATTERN.test(trimmed)
+  return (
+    ANCHORLESS_NUDGE_PATTERN.test(trimmed) &&
+    !TASK_REFERENCE_PATTERN.test(trimmed)
+  )
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function isAnchorlessNudge(text: string): boolean {
const trimmed = text.trim()
if (!trimmed || trimmed.length > MAX_NUDGE_CHARS) {
return false
}
return ANCHORLESS_NUDGE_PATTERN.test(trimmed)
const TASK_REFERENCE_PATTERN = /(?:task|)\s*#?\d+\b/i
export function isAnchorlessNudge(text: string): boolean {
const trimmed = text.trim()
if (!trimmed || trimmed.length > MAX_NUDGE_CHARS) {
return false
}
return (
ANCHORLESS_NUDGE_PATTERN.test(trimmed) &&
!TASK_REFERENCE_PATTERN.test(trimmed)
)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/api/taskAnchorReminder.ts` around lines 20 - 25, Update
isAnchorlessNudge to reject trimmed text containing an explicit task anchor
before applying ANCHORLESS_NUDGE_PATTERN, while preserving existing length and
nudge-word validation. Reuse the existing task-reference detection symbol if
available, and add regression coverage confirming inputs such as “continue Task
`#2`” return false.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant